﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Dokan;
using SharpShare.Storage;
using SharpShare.Diagnostics;
using SharpShare.Collections;

namespace SharpShare.Vfs {
    internal class StorageDokanOperations : DokanOperations, ILogProvider {
        public StorageDokanOperations(IStorageProvider provider) {
            this.StorageProvider = provider;
        }

        public IStorageProvider StorageProvider { get; private set; }

        private static Path<string> ParseFilename(string filename) {
            return Path.Parse(filename, componentSeparator: '\\', currentToken: null, parentToken: null);
        }
        private static string ToFilename(Path<string> path) {
            string pathFilename = "\\" + path.ToString("\\");
            return pathFilename;
        }

        private static void PopulateFileInformation(IStorageItem item, FileInformation fileinfo) {
            fileinfo.Attributes = System.IO.FileAttributes.Normal;

            if (item.Attributes.HasFlag(StorageItemAttributes.Hidden)) {
                fileinfo.Attributes |= System.IO.FileAttributes.Hidden;
            }

            if (item.Is<IStorageContainer>()) {
                fileinfo.Attributes |= System.IO.FileAttributes.Directory;
            }

            fileinfo.CreationTime = item.DateCreated;
            fileinfo.FileName = item.Name;
            fileinfo.LastAccessTime = item.DateModified;
            fileinfo.LastWriteTime = item.DateModified;

            IStorageFile file = item.AsFile();

            if (file != null) {
                fileinfo.Length = file.Length;
            }
        }

        private static FileInformation GetFileInformation(IStorageItem item) {
            FileInformation fileinfo = new FileInformation();

            PopulateFileInformation(item, fileinfo);

            return fileinfo;
        }

        #region DokanOperations Members

        int DokanOperations.Cleanup(string filename, DokanFileInfo info) {
            return DokanNet.DOKAN_SUCCESS;
        }

        int DokanOperations.CloseFile(string filename, DokanFileInfo info) {
            if (info.Context != null) {
                ((IStorageDataProvider)info.Context).Close();
            }
            return DokanNet.DOKAN_SUCCESS;
        }

        int DokanOperations.CreateDirectory(string filename, DokanFileInfo info) {
            try {
                Path<string> path = ParseFilename(filename);
                this.StorageProvider.Locate(path.Up).DemandContainer().CreateContainer(path.Last);

                info.IsDirectory = true;

                return DokanNet.DOKAN_SUCCESS;
            } catch (Exception ex) {
                return HandleException(ex);
            }
        }

        int DokanOperations.CreateFile(string filename, System.IO.FileAccess access, System.IO.FileShare share, System.IO.FileMode mode, System.IO.FileOptions options, DokanFileInfo info) {
            try {
                Path<string> path = ParseFilename(filename);

                IStorageItem located = this.StorageProvider.Locate(path);

                if (located != null && located.Is<IStorageContainer>()) {
                    info.IsDirectory = true;
                    return DokanNet.DOKAN_SUCCESS;
                }

                IStorageContainer container = this.StorageProvider.Locate(path.Up).DemandContainer();
                IStorageFile file = container .Contents[path.Last].AsFile();

                switch (mode) {
                    case System.IO.FileMode.Append:
                    case System.IO.FileMode.Open:
                        file.DemandExists();

                        break;
                    case System.IO.FileMode.OpenOrCreate:

                        break;
                    case System.IO.FileMode.Truncate:
                        if (file != null) {
                            file.Delete();
                            file = null;
                        }

                        break;
                    case System.IO.FileMode.CreateNew:
                        if (file != null && file.Exists) {
                            throw new StorageItemAlreadyExistsException();
                        }

                        break;
                    case System.IO.FileMode.Create:
                        if (file != null) {
                            file.Delete();
                            file = null;
                        }

                        break;
                }

                if (file == null) {
                    file = container.CreateFile(path.Last);
                }

                StorageFileAccess storageAccess = StorageFileAccess.None;
                StorageFileAccess storageShare = StorageFileAccess.None;

                switch (access) {
                    case System.IO.FileAccess.Read:
                        storageAccess = StorageFileAccess.Read;
                        break;
                    case System.IO.FileAccess.Write:
                        storageAccess = StorageFileAccess.Write;
                        break;
                    case System.IO.FileAccess.ReadWrite:
                        storageAccess = StorageFileAccess.ReadWrite;
                        break;
                }

                if (share.HasFlag(System.IO.FileShare.Read)) {
                    storageShare |= StorageFileAccess.Read;
                }
                if (share.HasFlag(System.IO.FileShare.Write)) {
                    storageShare |= StorageFileAccess.Write;
                }

                IStorageDataProvider dataProvider = file.Open(storageAccess, storageShare);

                info.Context = dataProvider;

                return DokanNet.DOKAN_SUCCESS;
            } catch (Exception ex) {
                return HandleException(ex);
            }
        }

        int DokanOperations.DeleteDirectory(string filename, DokanFileInfo info) {
            try {
                IStorageContainer container = this.StorageProvider.Locate(ParseFilename(filename)).DemandContainer();

                info.IsDirectory = true;

                container.Delete();

                return DokanNet.DOKAN_SUCCESS;
            } catch (Exception ex) {
                return HandleException(ex);
            }
        }

        int DokanOperations.DeleteFile(string filename, DokanFileInfo info) {
            return DokanNet.DOKAN_ERROR;
        }

        int DokanOperations.FindFiles(string filename, System.Collections.ArrayList files, DokanFileInfo info) {
            try {
                IStorageContainer container = this.StorageProvider.Locate(ParseFilename(filename)).DemandContainer();

                info.IsDirectory = true;

                foreach (IStorageItem item in container.Contents) {
                    files.Add(GetFileInformation(item));
                }

                return DokanNet.DOKAN_SUCCESS;
            } catch (Exception ex) {
                return HandleException(ex);
            }
        }

        int DokanOperations.FlushFileBuffers(string filename, DokanFileInfo info) {
            return DokanNet.DOKAN_SUCCESS;
        }

        int DokanOperations.GetDiskFreeSpace(ref ulong freeBytesAvailable, ref ulong totalBytes, ref ulong totalFreeBytes, DokanFileInfo info) {
            info.IsDirectory = true;

            freeBytesAvailable = this.StorageProvider.AvailableBytes;
            totalBytes = this.StorageProvider.TotalBytes;
            totalFreeBytes = freeBytesAvailable;

            return DokanNet.DOKAN_SUCCESS;
        }

        int DokanOperations.GetFileInformation(string filename, FileInformation fileinfo, DokanFileInfo info) {
            try {
                IStorageItem item = this.StorageProvider.Locate(ParseFilename(filename)).DemandExists();

                if (!info.IsDirectory) {
                    if (item.Is<IStorageContainer>()) {
                        info.IsDirectory = true;
                    }
                }

                PopulateFileInformation(item, fileinfo);

                return DokanNet.DOKAN_SUCCESS;
            } catch (Exception ex) {
                return HandleException(ex);
            }
        }

        int DokanOperations.LockFile(string filename, long offset, long length, DokanFileInfo info) {
            if (info.Context == null) {
                return DokanNet.DOKAN_ERROR;
            }

            return DokanNet.DOKAN_SUCCESS;
        }

        int DokanOperations.MoveFile(string filename, string newname, bool replace, DokanFileInfo info) {
            return DokanNet.DOKAN_ERROR;
        }

        int DokanOperations.OpenDirectory(string filename, DokanFileInfo info) {
            try {
                IStorageContainer container = this.StorageProvider.Locate(ParseFilename(filename)).DemandContainer();

                info.IsDirectory = true;

                return DokanNet.DOKAN_SUCCESS;
            } catch (Exception ex) {
                return HandleException(ex);
            }
        }

        int DokanOperations.ReadFile(string filename, byte[] buffer, ref uint readBytes, long offset, DokanFileInfo info) {
            if (info.Context == null) {
                return DokanNet.DOKAN_ERROR;
            }

            IStorageDataProvider provider = (IStorageDataProvider)info.Context;

            int bytesRead = provider.Read(offset, buffer, 0, buffer.Length);

            readBytes = (uint)bytesRead;

            return DokanNet.DOKAN_SUCCESS;
        }

        int DokanOperations.SetAllocationSize(string filename, long length, DokanFileInfo info) {
            return DokanNet.DOKAN_SUCCESS;
        }

        int DokanOperations.SetEndOfFile(string filename, long length, DokanFileInfo info) {
            try {
                IStorageDataProvider provider = (IStorageDataProvider)info.Context;

                provider.SetLength(length);

                return DokanNet.DOKAN_SUCCESS;
            } catch (Exception ex) {
                return HandleException(ex);
            }
        }

        int DokanOperations.SetFileAttributes(string filename, System.IO.FileAttributes attr, DokanFileInfo info) {
            throw new NotImplementedException();
        }

        int DokanOperations.SetFileTime(string filename, DateTime ctime, DateTime atime, DateTime mtime, DokanFileInfo info) {
            throw new NotImplementedException();
        }

        int DokanOperations.UnlockFile(string filename, long offset, long length, DokanFileInfo info) {
            throw new NotImplementedException();
        }

        int DokanOperations.Unmount(DokanFileInfo info) {
            return DokanNet.DOKAN_SUCCESS;
        }

        int DokanOperations.WriteFile(string filename, byte[] buffer, ref uint writtenBytes, long offset, DokanFileInfo info) {
            try {
                IStorageDataProvider provider = (IStorageDataProvider)info.Context;

                provider.Write(offset, buffer, 0, buffer.Length);

                writtenBytes = (uint)buffer.Length;

                return DokanNet.DOKAN_SUCCESS;
            } catch (Exception ex) {
                return HandleException(ex);
            }
        }

        #endregion

        private int HandleException(Exception ex) {
            if (ex is StorageItemNotFoundException) {
                return DokanNet.ERROR_FILE_NOT_FOUND;
            }

            if (ex is StorageItemLockedException || ex is StorageItemInUseException) {
                return DokanNet.ERROR_SHARING_VIOLATION;
            }

            if (ex is StorageItemAlreadyExistsException) {
                return DokanNet.ERROR_FILE_EXISTS;
            }

            if (ex is StorageItemInvalidTypeException) {
                return DokanNet.ERROR_FILE_NOT_FOUND;
            }

            Log.Add(this, EntryType.Error, "Unhandled exception: {0}", ex);

            return DokanNet.DOKAN_ERROR;
            //throw new Exception("Unhandled exception.", ex);
        }

        #region ILogProvider Members

        string ILogProvider.Name {
            get { return "VFS"; }
        }

        #endregion
    }
}
